package br.com.wryel.dao.impl;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EmbeddedId;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import br.com.wryel.dao.DAOException;
import br.com.wryel.dao.GenericDAO;
import br.com.wryel.helper.ReflectionHelper;

public abstract class GenericJPADAOImpl<Entity extends Serializable> implements GenericDAO<Entity> {
	
	protected final Class<Entity> entityClass;
	
	@PersistenceContext
	protected EntityManager entityManager;

	public GenericJPADAOImpl(Class<Entity> entityClass) {
		this.entityClass = entityClass;
	}

	public void save(Entity entity) throws DAOException {
		if (isNew(entity)) {
			insert(entity);
	    } else {
	    	update(entity);
	    }
	}
	
	public Object getKey(Object bean) throws DAOException {	
		try {

			List<Field> fields = ReflectionHelper.getField(bean, Id.class);

			if (fields.isEmpty()) {
				fields = ReflectionHelper.getField(bean, EmbeddedId.class);
			}

			if (!fields.isEmpty()) {

				Object result = ReflectionHelper.getObjectProprety(bean, fields.get(0).getName());

				if (result != null && result instanceof Number && ((Number) result).doubleValue() == 0) {
					result = null;
				}

				return result;

			} else {
				throw new DAOException("O bean nao tem chave configurada: " + bean.getClass().getName());
			}

		} catch (Exception exception) {
			throw new DAOException("Nao foi possivel recuperar a chave do objeto.", exception);
		}
	}

	protected boolean isNew(Entity entity) throws DAOException {
		return getKey(entity) == null;
	}

	protected void update(Entity entity) {
		entityManager.merge(entity);
		entityManager.flush();
	}

	protected void insert(Entity entity) {
		entityManager.persist(entity);
		entityManager.flush();
	}

	public Entity retrieve(Entity entity) throws DAOException {
		Entity retriviedEntity = entityManager.find(entityClass, getKey(entity));
		return retriviedEntity;
	}

	public void delete(Entity entity) {
		entityManager.remove(entity);
		entityManager.flush();
	}

	protected EntityManager getEntityManager() {
		return entityManager;
	}

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
	
	protected List<Entity> executeNamedQuery(String name) {
		List<Entity> list = executeNamedQuery(name, new HashMap<String, Object>());
		return list;
	}

	protected List<Entity> executeNamedQuery(String name, Map<String, Object> params) {
		List<Entity> list = executeNamedQuery(name, entityClass, params);
		return list;
	}
	
	protected <Klass extends Serializable> List<Klass> executeNamedQuery(String name, Class<Klass> klass) {
		List<Klass> list = executeNamedQuery(name, klass, new HashMap<String, Object>());
		return list;
	}
	
	protected <Klass extends Serializable> List<Klass> executeNamedQuery(String name, Class<Klass> klass, Map<String, Object> params) {
		
		TypedQuery<Klass> typedQuery = entityManager.createNamedQuery(name, klass);
		
		for (String key : params.keySet()) {
			Object value = params.get(key);
			typedQuery.setParameter(key, value);
		}
		
		List<Klass> result = typedQuery.getResultList();
		
		return result;
	}	
	
	protected List<Entity> executeJPAQL(String jpaQL) {
		List<Entity> result = executeJPAQL(jpaQL, entityClass);
		return result;
	}
	
	protected List<Entity> executeJPAQL(String jpaQL, Map<String, Object> params) {
		List<Entity> result = executeJPAQL(jpaQL, entityClass, params);
		return result;
	}
	
	protected <Klass extends Serializable> List<Klass> executeJPAQL(String jpaQL, Class<Klass> klass) {
		List<Klass> result = executeJPAQL(jpaQL, klass, new HashMap<String, Object>());
		return result;
	}
	
	protected <Klass extends Serializable> List<Klass> executeJPAQL(String jpaQL, Class<Klass> klass, Map<String, Object> params) {
		
		TypedQuery<Klass> typedQuery = entityManager.createQuery(jpaQL, klass);

		for (String key : params.keySet()) {
			Object value = params.get(key);
			typedQuery.setParameter(key, value);
		}
		
		List<Klass> result = typedQuery.getResultList();
		
		return result;
	}
	
	protected <Klass extends Serializable> List<Klass> executeNativeQuery(String sql, Class<Klass> klass) {
		List<Klass> result = executeNativeQuery(sql, klass, new ArrayList<Object>());
		return result;
	}
	
	@SuppressWarnings("unchecked")
	protected <Klass extends Serializable> List<Klass> executeNativeQuery(String sql, Class<Klass> klass, List<Object> params) {
		
		Query query = entityManager.createNativeQuery(sql, klass);
		
		for (int x = 1; x < params.size() + 1; x++) {
			query.setParameter(x, params.get(x));
		}
		
		List<Klass> list = query.getResultList();
		
		return list;
	}
}
